package com.saicmobility.taxi.iov.media.extract

import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaCodec
import android.media.MediaCodecInfo
import android.media.MediaFormat
import android.util.Log
import com.czt.mp3recorder.util.LameUtil
import com.saic.log.SCLog
import com.saicmobility.taxi.iov.netinterface.utils.TransformUtils
import com.saicmobility.taxi.iov.system.CameraChannel
import com.saicmobility.taxi.iov.system.SystemUtils
import com.saicmobility.taxi.iov.system.bean.AudioStream
import com.saicmobility.taxi.iov.system.bean.VideoStream
import com.saicmobility.taxi.iov.system.listener.AudioStreamListener
import com.saicmobility.taxi.iov.system.listener.VideoStreamListener
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.jcodec.codecs.aac.AACDecoder
import org.jcodec.codecs.h264.BufferH264ES
import org.jcodec.codecs.h264.H264Decoder
import org.jcodec.common.Codec
import org.jcodec.common.MuxerTrack
import org.jcodec.common.io.NIOUtils
import org.jcodec.common.model.Packet
import org.jcodec.containers.mp4.muxer.MP4Muxer
import java.io.File
import java.io.FileOutputStream
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.ThreadFactory
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger


/**
 * 媒体文件编码单例类
 * 2021/10/29 10:41
 * @author chenzhiyuan
 */
object MediaEncode {

    private const val TAG = "MediaEncode"

    private val threadFactory =
        ThreadFactory { r ->
            val thread = Thread(r)
            thread.name = "MediaEncode_" + "Thread_" + thread.id
            thread
        }

    private val threadPool by lazy {
        ThreadPoolExecutor(
            0, 1,
            0L, TimeUnit.MILLISECONDS,
            LinkedBlockingQueue(),
            threadFactory
        )
    }

    private const val DEFAULT_SAMPLING_RATE = 8000
    private const val DEFAULT_LAME_MP3_QUALITY = 7
    private const val DEFAULT_LAME_IN_CHANNEL = 1
    private const val DEFAULT_LAME_MP3_BIT_RATE = 128

    /**
     * 编码视频
     * @param cameraChannel 摄像Channel
     * @param startTime 开始时间 时间戳
     * @param duration 媒体文件时长 单位：毫秒
     * @param targetFile 生成目标文件
     * @param result 回调函数
     */
    fun encodeVideo(
        cameraChannel: CameraChannel,
        startTime: Long,
        duration: Long,
        targetFile: File,
        result: (b: Boolean) -> Unit
    ) {
        threadPool.execute {
            if (SystemUtils.searchVideoStream(cameraChannel, startTime, duration) == null) {
                result.invoke(false)
                return@execute
            }
            runBlocking {
                if (targetFile.exists()) {
                    targetFile.delete()
                }
                if (targetFile.parentFile?.exists() != true) {
                    targetFile.parentFile?.mkdirs()
                }

                val h264File = File(targetFile.parent, targetFile.nameWithoutExtension + ".h264")
                val h264List = arrayListOf<ByteArray>()

                val aacFile = File(targetFile.parent, targetFile.nameWithoutExtension + ".aac")
                val pcmQueue = LinkedBlockingQueue<AudioStream>()

                //末尾帧是否load完成
                val endFrame = AtomicBoolean(false)

                //单位时间内帧数
                val unitTimeFrames = AtomicInteger(0)
//                val between_second = duration/1000;
//                SCLog.i(TAG,"debug between_second is:$between_second")
//                val frame_limit = 15*between_second
//                val frame_count = AtomicInteger(0)
//                SCLog.i(TAG,"debug frame_count is:${frame_limit}")
                val checkFramesJob = GlobalScope.async {
                    repeat(Int.MAX_VALUE) {
                        delay(5000)
                        val frames = unitTimeFrames.get()
                        SCLog.i(TAG, "check unitTime Frames | frames =$frames")
                        if (frames > 0) {
                            unitTimeFrames.set(0)
                        } else {
                            SCLog.i(TAG, "checkFramesJob set true")
                            endFrame.set(true)
                        }
                    }
                }

                var startTimeUs: Long? = null
                SystemUtils.cameraStartHistoryStream(
                    cameraChannel,
                    SimpleDateFormat(
                        "yyyyMMddHHmmss",
                        Locale.getDefault()
                    ).format(Date(startTime)),
                    object : VideoStreamListener {
                        override fun onReceived(stream: VideoStream) {
                            unitTimeFrames.incrementAndGet()
                            //frame_count.incrementAndGet()

                            if (!endFrame.get()) {

                                h264List.add(stream.buffer.sliceArray(0 until stream.length))
                            }

                            if (startTimeUs == null) {
                                startTimeUs = stream.pts
                                SCLog.i(TAG, "onReceived: START_OF_STREAM startTimeUs:$startTimeUs")
                            }

                            //SCLog.i(TAG, "EVERY pts:${stream.pts}")
                            val betweenTime = stream.pts - (startTimeUs ?: 0L)
                            //SCLog.i(TAG, "EVERY betweenTime:$betweenTime")
                            if (betweenTime >= duration) {
                                endFrame.set(true)
                                SCLog.i(TAG, "onReceived: endFrame = true")
                            }
                            //帧数兜底
//                            if(frame_count.get() >= frame_limit){
//                                endFrame.set(true)
//                                SCLog.i(TAG, "framle limit onReceived: endFrame = true frame_count = $frame_count")
//                            }

                        }
                    },
                    object : AudioStreamListener {
                        override fun onReceived(stream: AudioStream) {
                            pcmQueue.offer(stream.also {
                                stream.pts = it.pts * 1000
                                stream.buffer = it.buffer.copyOfRange(0, it.length)
                            })
                        }
                    })

                while (true) {
                    if (endFrame.get()) {
                        SCLog.i(TAG, "stop cameraStopHistoryStream")
                        SystemUtils.cameraStopHistoryStream(cameraChannel)
                        checkFramesJob.cancelAndJoin()
                        break
                    }
                }

                if (h264List.isEmpty()) {
                    result.invoke(false)
                    return@runBlocking
                }

                SCLog.d(TAG, "save File ...")
                //h264文件
                val asyncH264ToFile = GlobalScope.async {
                    saveByteToFile(h264List, h264File.path)
                }

                val asyncCodecAudio = GlobalScope.async {
                    val audioCodec = initAudioMediaCodec()
                    SCLog.d(TAG, "audioCodec start")
                    audioCodec.start()
                    var needGiveUp = true
                    FileOutputStream(aacFile.path).use { os ->
                        audioCodec@ while (true) {
                            // 取出InputBuffer，填充音频数据，然后输送到编码器进行编码
                            val inputBufferIndex: Int = audioCodec.dequeueInputBuffer(-1)
                            if (inputBufferIndex >= 0) {
                                val inputBuffer: ByteBuffer? =
                                    audioCodec.getInputBuffer(inputBufferIndex)
                                val poll = pcmQueue.poll()
                                poll ?: continue

                                inputBuffer?.clear()
                                inputBuffer?.put(poll.buffer)
                                audioCodec.queueInputBuffer(
                                    inputBufferIndex,
                                    0,
                                    poll.buffer.size,
                                    poll.pts,
                                    0
                                )
                            }

                            val mBufferInfo = MediaCodec.BufferInfo()
                            var outputBufferIndex: Int =
                                audioCodec.dequeueOutputBuffer(mBufferInfo, 1000)
                            while (outputBufferIndex >= 0) {
                                val outputBuffer: ByteBuffer? =
                                    audioCodec.getOutputBuffer(outputBufferIndex)
                                outputBuffer ?: continue
                                //SCLog.i("ZXG999","position:${outputBuffer.position()},limit:${outputBuffer.limit()}")
                                val outBufferSize = outputBuffer.limit() + 7
                                if(outBufferSize > 9){
                                    //第一帧可能是脏数据，直接丢掉(丢了不影响播放)
                                    if(needGiveUp){
                                        //SCLog.i("ZXG999","give up first frame")
                                       needGiveUp = false
                                    }else{
                                        val aacBytes = ByteArray(outBufferSize)
                                        addADTStoPacket(aacBytes, outBufferSize)
                                        outputBuffer[aacBytes, 7, outputBuffer.limit()]
                                        val str = TransformUtils.byteToHexString(0,aacBytes,20)
                                        //SCLog.i("ZXG999","aac header:${str}")
                                        os.write(aacBytes)
                                    }
                                }

                                audioCodec.releaseOutputBuffer(outputBufferIndex, false)
                                outputBufferIndex =
                                    audioCodec.dequeueOutputBuffer(mBufferInfo, 1000)
                            }
                            if (endFrame.get() && pcmQueue.isEmpty()) {
                                SCLog.d(TAG, "audioCodec finish")
                                break@audioCodec
                            }
                        }
                    }
                    audioCodec.also {
                        it.stop()
                        it.release()
                    }
                }

                asyncH264ToFile.await()
                h264List.clear()
                asyncCodecAudio.await()
                pcmQueue.clear()

                SCLog.d(TAG, "create Movie File ...")
                try {
//                    val videoFile = File("path/to/video/file.mp4")
//                    val audioFile = File("path/to/audio/file.mp3")
//                    val outputFile = File("path/to/output/merged.mp4")
//
//                    val muxer: MP4Muxer = MP4Muxer.make(outputFile)
//                    muxer.openVideo(1920, 1080, Rational.NTSC, "h264")
//                    muxer.openAudio(2, 48000, 2, "aac")
//
//                    var videoPacket: MP4Packet?
//                    var audioPacket: MP4Packet?
//                    while (muxer.readVideoPacket(videoFile).also { videoPacket = it } != null) {
//                        muxer.writeVideoPacket(videoPacket)
//                    }
//                    while (muxer.readAudioPacket(audioFile).also { audioPacket = it } != null) {
//                        muxer.writeAudioPacket(audioPacket)
//                    }
//
//                    muxer.close()

//                    val movie = Movie()
//                    val h264Track = H264TrackImpl(FileDataSourceImpl(h264File.path))
//                    movie.addTrack(h264Track)
//
//                    val aacTrack = AACTrackImpl(FileDataSourceImpl(aacFile.path))
//                    movie.addTrack(aacTrack)
//
//                    val mp4File = DefaultMp4Builder().build(movie)
//                    val fc = FileOutputStream(targetFile).channel
//                    mp4File.writeContainer(fc)
//                    fc.close()

//                    FileOutputStream(targetFile).use {
//                        val fc = it.channel
//                        mp4File.writeContainer(fc)
//                    }

                    //MergeAudioVideo.muxVideoAndAudio(h264File.path, aacFile.path, targetFile.path)
                    VideoAudioMuxer.muxVideoAndAudio2(h264File.path, aacFile.path, targetFile.path)

//                    val output = NIOUtils.writableChannel(targetFile)
//                    val muxer = MP4Muxer.createMP4MuxerToChannel(output)
//
//                    muxVideo(muxer, h264File)
//                    muxAudio(muxer, aacFile)
//
//                    muxer.finish()
//                    output.close()

//                    val video = MovieCreator.build(h264File.path)
//                    val audio = MovieCreator.build(aacFile.path)
//                    val movie = Movie()
//
//                    //创建视、音频帧集
//                    //创建视、音频帧集
//                    val videoTrack: MutableList<Track> = LinkedList()
//                    val audioTrack: MutableList<Track> = LinkedList()
//                    //遍历获得视、音频帧集
//                    //遍历获得视、音频帧集
//                    for (t in video.tracks) {
//                        videoTrack.add(t)
//                    }
//                    for (t in audio.tracks) {
//                        audioTrack.add(t)
//                    }
//                    if (audioTrack.size <= 0 || videoTrack.size <= 0) {
//                        println("error")
//                    }
//                    //添加视频、音频帧集
//                    //添加视频、音频帧集
//                    movie.addTrack(AppendTrack(*audioTrack.toTypedArray<Track>()))
//                    movie.addTrack(AppendTrack(*videoTrack.toTypedArray<Track>()))
//                    //创建mp4构造器
//                    val mp4File = DefaultMp4Builder().build(movie)
//                    FileOutputStream(targetFile).use {
//                        val fc = it.channel
//                        mp4File.writeContainer(fc)
//                    }

                    SCLog.i(TAG, "create Movie File Finish")
                    result.invoke(true)
                } catch (e: Exception) {
                    SCLog.e(TAG, "create Movie File Exception: ${e.message}")
                    result.invoke(false)
                } finally {
                    h264File.delete()
                    aacFile.delete()
                }
            }
        }
    }

    private fun muxVideo(muxer: MP4Muxer, videoFile: File) {
        var track: MuxerTrack? = null
        val es = BufferH264ES(NIOUtils.mapFile(videoFile))

        var frame: Packet? = null
        while (es.nextFrame().also { frame = it } != null) {
            if (track == null) {
                track = muxer.addVideoTrack(Codec.H264, H264Decoder().getCodecMeta(frame?.getData()))
            }
            track?.addFrame(frame)
        }
    }

    private fun muxAudio(muxer: MP4Muxer, audioFile: File) {
        var track: MuxerTrack? = null
        val es = BufferH264ES(NIOUtils.mapFile(audioFile))

        var frame: Packet? = null
        while (es.nextFrame().also { frame = it } != null) {
            if (track == null) {
                track = muxer.addAudioTrack(Codec.AAC, AACDecoder(frame?.getData()).getCodecMeta(frame?.getData()))
            }
            track?.addFrame(frame)
        }
    }

    /**
     * 编码音频
     * @param startTime 开始时间 时间戳
     * @param duration 媒体文件时长 单位：毫秒
     * @param targetFile 生成目标文件
     * @param result 回调函数
     */
    fun encodeAudio(
        startTime: Long,
        duration: Long,
        targetFile: File,
        result: (b: Boolean) -> Unit
    ) {
        threadPool.execute {
            if (SystemUtils.searchVideoStream(CameraChannel.OUT, startTime, duration) == null) {
                result.invoke(false)
                return@execute
            }
            runBlocking {
                if (targetFile.exists()) {
                    targetFile.delete()
                }
                if (targetFile.parentFile?.exists() != true) {
                    targetFile.parentFile?.mkdirs()
                }

                //初始化
                SCLog.d(TAG, "LameUtil init")
                LameUtil.init(
                    DEFAULT_SAMPLING_RATE,
                    DEFAULT_LAME_IN_CHANNEL,
                    DEFAULT_SAMPLING_RATE,
                    DEFAULT_LAME_MP3_BIT_RATE,
                    DEFAULT_LAME_MP3_QUALITY
                )

                var mShortPcmBuffer: ShortArray? = null
                var mMp3Buffer: ByteArray? = null
                val pcmQueue = LinkedBlockingQueue<AudioStream>()

                //末尾帧是否load完成
                val endFrame = AtomicBoolean(false)

                //单位时间内帧数
                val unitTimeFrames = AtomicInteger(0)
                val checkFramesJob = GlobalScope.async {
                    repeat(Int.MAX_VALUE) {
                        delay(5000)
                        val frames = unitTimeFrames.get()
                        SCLog.d(TAG, "check unitTime Frames | frames =$frames")
                        if (frames > 0) {
                            unitTimeFrames.set(0)
                        } else {
                            endFrame.set(true)
                        }
                    }
                }

                var startTimeUs: Long? = null
                SystemUtils.cameraStartHistoryStream(
                    CameraChannel.OUT,
                    SimpleDateFormat(
                        "yyyyMMddHHmmss",
                        Locale.getDefault()
                    ).format(Date(startTime)),
                    null,
                    object : AudioStreamListener {
                        override fun onReceived(stream: AudioStream) {
                            if (!endFrame.get()) {
                                pcmQueue.offer(stream.also {
                                    it.buffer = it.buffer.copyOfRange(0, it.length)
                                })
                            }

                            if (startTimeUs == null) {
                                Log.d(
                                    TAG,
                                    "onReceived: START_OF_STREAM"
                                )
                                startTimeUs = stream.pts
                            }

                            val betweenTime = stream.pts - (startTimeUs ?: 0L)
                            if (betweenTime >= duration) {
                                endFrame.set(true)
                                Log.d(
                                    TAG,
                                    "onReceived: endFrame = true | pts = ${stream.pts}"
                                )
                            }
                        }
                    })

                while (true) {
                    if (endFrame.get()) {
                        SCLog.d(TAG, "stop cameraStopHistoryStream")
                        SystemUtils.cameraStopHistoryStream(CameraChannel.OUT)
                        checkFramesJob.cancelAndJoin()
                        break
                    }
                }

                if (pcmQueue.isEmpty()) {
                    result.invoke(false)
                    return@runBlocking
                }

                try {
                    FileOutputStream(targetFile).use { fos ->
                        while (!endFrame.get() || pcmQueue.isNotEmpty()) {
                            val stream = pcmQueue.poll()
                            stream ?: continue
                            val data = stream.buffer

                            if (mShortPcmBuffer == null || mMp3Buffer == null) {
                                mShortPcmBuffer = ShortArray(data.size / 2)
                                mMp3Buffer = ByteArray((7200 + (data.size * 2 * 1.25)).toInt())
                            }

                            //to short array
                            ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer()
                                .get(mShortPcmBuffer)

                            //编码
                            val encodedSize = LameUtil.encode(
                                mShortPcmBuffer,
                                mShortPcmBuffer,
                                mShortPcmBuffer?.size ?: 0,
                                mMp3Buffer
                            )

                            //取出待编码的数据
                            fos.write(mMp3Buffer, 0, encodedSize)
                        }

                        val flushResult = LameUtil.flush(mMp3Buffer)
                        if (flushResult > 0) {
                            fos.write(mMp3Buffer, 0, flushResult)
                        }
                    }
                    result.invoke(true)
                } catch (e: Exception) {
                    SCLog.e(TAG, "create Audio File Exception: ${e.message}")
                    result.invoke(false)
                } finally {
                    LameUtil.close()
                    SCLog.d(TAG, "encode finish")
                }
            }
        }
    }


    /**
     * 初始化音频编码器
     */
    private fun initAudioMediaCodec(): MediaCodec {
        /*音频Format*/
        //采样率
        val sampleRate = 8000
        val format =
            MediaFormat.createAudioFormat(
                MediaFormat.MIMETYPE_AUDIO_AAC,
                sampleRate,
                1
            ).also {
                it.setInteger(
                    MediaFormat.KEY_AAC_PROFILE,
                    MediaCodecInfo.CodecProfileLevel.AACObjectLC
                )
                //比特率
                it.setInteger(MediaFormat.KEY_BIT_RATE, 16 * sampleRate)
                it.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_IN_MONO)

                val mBufferSize = AudioRecord.getMinBufferSize(
                    sampleRate,
                    AudioFormat.CHANNEL_IN_MONO,
                    AudioFormat.ENCODING_PCM_16BIT
                )
                it.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, mBufferSize * 4)
            }

        return MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC).also {
            //设置编码器
            it.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
        }
    }

    private fun addADTStoPacket(packet: ByteArray, packetLen: Int) {
        val profile = 2
        //11对应8k的采样率。参见https://blog.csdn.net/BSPLover/article/details/7426476
        val freqIdx = 11
        //声道设置，具体对应https://blog.csdn.net/BSPLover/article/details/7426476。
        val chanCfg = 1
        packet[0] = 0xFF.toByte()
        packet[1] = 0xF1.toByte()
        packet[2] = ((profile - 1 shl 6) + (freqIdx shl 2) + (chanCfg shr 2)).toByte()
        packet[3] = ((chanCfg and 3 shl 6) + (packetLen shr 11)).toByte()
        packet[4] = (packetLen and 0x7FF shr 3).toByte()
        packet[5] = ((packetLen and 7 shl 5) + 0x1F).toByte()
        packet[6] = 0xFC.toByte()
    }

    private fun saveByteToFile(data: List<ByteArray>, dstPath: String?): Boolean {
        var success: Boolean
        FileOutputStream(dstPath).use { fs ->
            for (items in data) {
                fs.write(items)
            }
            success = true
        }
        return success
    }
}